package evolution_superclasses;

import java.util.ArrayList;
import java.util.Random;

import Generic.Generic_Selection_Mechanism;
import Generic.Generic_Selection_Protocol;

import misc.Input_Handler;
import misc.Plotter;
import misc.not_Legal_Exception;

public abstract class Evolutionary_Loop {
	protected Random _rand;
	protected Input_Handler _ih;
	protected Plotter _plotter;
	protected ArrayList<Double> _max, _avg, _min, _stdDerivance;
	protected Generic_Selection_Protocol _bsp;
	protected Generic_Selection_Mechanism _bsm;
	protected Phenotype[] _generation;

	protected boolean _finished = false, _print = false, _printEach = false, _plot = true;
	protected int _genSize, _popSize, _adultPoolsize, _origGenerationLimit, _generationLimit, _freepass, _generationNumber;
	protected double _mutationRate, _crossOverRate, _endBestFitness, _bestFitness, _totalFitness, _worstFitness;
	private String _title = "Result", _filename = "chart";
	protected Genotype _bestInduvidual;
	private boolean[] _plotList;

	public String get_title() {
		return _title;
	}

	public void set_title(String title) {
		_title = title;
	}

	public String get_filename() {
		return _filename;
	}

	public void set_filename(String filename) {
		_filename = filename;
	}

	public int get_generationNumber() {
		return _generationNumber;
	}

	public void set_generationNumber(int generationNumber) {
		_generationNumber = generationNumber;
	}

	public void incrementGenerationNumber() {
		_generationNumber++;
	}

	public double get_bestFitness() {
		return _bestFitness;
	}

	public void set_bestFitness(double bestFitness) {
		_bestFitness = bestFitness;
	}

	public double get_totalFitness() {
		return _totalFitness;
	}

	public void set_totalFitness(double totalFitness) {
		_totalFitness = totalFitness;
	}

	public Genotype get_bestInduvidual() {
		return _bestInduvidual;
	}

	public void set_bestInduvidual(Genotype bestInduvidual) {
		_bestInduvidual = bestInduvidual;
	}

	public double get_worstFitness() {
		return _worstFitness;
	}

	public void set_worstFitness(double worstFitness) {
		_worstFitness = worstFitness;
	}

	public boolean[] get_plotList() {
		return _plotList;
	}

	public void set_plotList(boolean[] plotList) {
		_plotList = plotList;
	}
	
	public Evolutionary_Loop(){
		_ih = new Input_Handler();
		_rand = new Random();
		_plotter = new Plotter();
		_max = new ArrayList<Double>();
		_avg = new ArrayList<Double>();
		_min = new ArrayList<Double>();
		_stdDerivance = new ArrayList<Double>();
		System.setProperty("java.util.Arrays.useLegacyMergeSort","true");
		
	}
	
	//Ignore values, overwrite them in subclass. it's just here to show work flow.
	public void begin() throws Exception{
		clearArrayLists();
		set_title("Title");
		set_filename("chart");
		_plot = false;
		
		initializePrint(true);
		initializeEndConditions(-1);
		initializePopulationSize(-1);
		initializeGenotypeSize(-1);
		initializeGeneticOperatiors(-1,-1);
		initializeEliteism(-1);
		initializeFitnessEvaluation(-1);
		initializeAdultPoolSize(-1);
		initializeSelectionProtocol(-1);
		initializePopulation();
		initializeSelectionMechanism(-1);
		
		evolutionLoop();
		if(!_print)//Print them anyway
			printEndResults();
	}
	
	public void evolutionLoop() throws not_Legal_Exception{
		while(_generationLimit-->0){
			if(_finished)return;
			initializeAndDevelopGeneration();
			
			runAdultSelection();
			runParentSelection();
			
			incrementGenerationNumber();
			createNewPopulation();
		}
		finished();
	}
	
	protected void clearArrayLists(){
		_max.clear();
		_avg.clear();
		_min.clear();
		_stdDerivance.clear();
	}
	
	protected void initializePrint(boolean ask) {
		if(!ask){
			_print = true;//defaults
			_printEach = false;
			return;
		}
		System.out.println("Welcome \n Would you like to print information about the genes? (1/0)");
		try {
			_print = _ih.readInt()==1; //Otherwise, just assume no
			System.out.println("Would you like to print information about each of the generations? (1/0)");
			_printEach = _ih.readInt()==1; //Otherwise, just assume no
		} catch (Exception e) {
			System.out.println("Illegal value detected, try again");
			initializePrint(ask);
		}
	}
	
	protected void initializeEndConditions(int i){
		if(i>-1){
			_generationLimit = i;
			_finished = false;
			_origGenerationLimit = _generationLimit;
			set_generationNumber(1);
			return;
		}
		System.out.println("How many generations are your simulation limit? (int)");
		try {
			_generationLimit = _ih.readInt()-1;
			if(_generationLimit < 0) throw new IllegalArgumentException();
			set_generationNumber(1);
		} catch (Exception e) {
			System.out.println("Illegal value detected, try again");
			initializeEndConditions(i);
		}
		_origGenerationLimit = _generationLimit;
	}
	
	protected void initializeEliteism(int i) {
		if(i>-1){
			_freepass = i;
			if(_freepass < 0 || _freepass > _popSize) throw new IllegalArgumentException();
			return;
		}
		System.out.println("How many of your best parents get's trough unchanged? (int)");
		try {
			_freepass = _ih.readInt();
			if(_freepass < 0 || _freepass > _popSize) throw new IllegalArgumentException();
		} catch (Exception e) {
			System.out.println("Illegal value detected, try again");
			initializeEliteism(i);
		}
	}
	
	protected void initializePopulationSize(int i) {
		if(i>-1){
			_popSize = i;
			return;
		}
		System.out.println("How big do you want your population? (int)");
		try {
			_popSize = _ih.readInt();
		} catch (Exception e) {
			System.out.println("Illegal value detected, try again");
			initializePopulationSize(i);
		}
	}

	protected void initializeGenotypeSize(int i) {
		if(i>-1){
			_genSize = i;
			return;
		}
		System.out.println("How long do you fancy your genotypes? (int)");
		try {
			_genSize = _ih.readInt();
		} catch (Exception e) {
			System.out.println("Illegal value detected, try again");
			initializeGenotypeSize(i);
		}
	}

	protected void initializeAdultPoolSize(int i) {
		if(i>-1){
			int tmp = i;
			_adultPoolsize = tmp==0||tmp>_popSize?_popSize:tmp;
			return;
		}
		System.out.println("How big ought the adult pool size be? (0 makes it as big as the population, and it cannot be bigger than this)");
		try {
			int tmp = _ih.readInt();
			_adultPoolsize = tmp==0||tmp>_popSize?_popSize:tmp;
		} catch (Exception e) {
			System.out.println("Illegal value detected, try again");
			initializeAdultPoolSize(i);
		}
	}
	
	protected void finished() {
		setScoreInduvidual();
		_finished = true;
		if(_plot)_plotter.plotData(get_title(), get_filename(), _max, _avg, _min, _stdDerivance, get_plotList());
		if(_print)printEndResults();
	}
	
	protected void printEndResults() {
		System.out.println("End fitness: "+_endBestFitness+" after "+(get_generationNumber()-1)+" generations");
		if(_print){
			System.out.println("The best individual was this this:");
			System.out.println(_bestInduvidual);
		}
	}

	protected void initializeSelectionProtocol(int i) {
		if(i>-1){
			int tmp = i;
			if(tmp==0 && _adultPoolsize < _popSize)tmp = 1;
			_bsp = new Generic_Selection_Protocol(tmp, _adultPoolsize);
			_bsp.set_adultPool(new Phenotype[_adultPoolsize]);
			return;
		}
		System.out.println("Next up, which selection protocol would you like?"+
				"\n(0 is full generation replacement)"+
				"\n(1 is overproduction)"+
				"\n(2 is generation mixing)");
		try {
			int tmp = _ih.readInt();
			if(tmp==0 && _adultPoolsize < _popSize){
				System.out.println("Cannot have full generation replacement if the adultpool is smaller than the population, automatic change to overproduction");
				tmp = 1;
			}
			_bsp = new Generic_Selection_Protocol(tmp, _adultPoolsize);
			_bsp.set_adultPool(new Phenotype[_adultPoolsize]);
		} catch (Exception e) {
			System.out.println("Illegal value detected, try again");
			initializeSelectionProtocol(i);
		}
	}

	protected void initializeSelectionMechanism(int i) throws not_Legal_Exception {
		if(i>-1){
			_bsm = new Generic_Selection_Mechanism(i, _popSize-_freepass);//Ensure all selected will be used
			return;
		}
		System.out.println("Soon done, which selection mechanism would you like?"+
				"\n(0 is fitness-proportionate)"+
				"\n(1 is sigma-scaling)"+
				"\n(2 is rank selection)"+
				"\n(3 is tournament selection)");
		try {
			_bsm = new Generic_Selection_Mechanism(_ih.readInt(), _popSize-_freepass);//Ensure all selected will be used
		} catch (Exception e) {
			System.out.println("Illegal value detected, try again");
			initializeSelectionMechanism(i);
		}
	}

	protected void runAdultSelection() throws not_Legal_Exception {
		_bsp.selection(_generation);
	}

	protected void runParentSelection() throws not_Legal_Exception {
		_bsm.selection(_bsp.get_adultPool());
	}
	
	protected void setScoreInduvidual() {
		_endBestFitness = get_bestFitness();
		_bestInduvidual = get_bestInduvidual();
		
	}
	
	protected abstract void initializeGeneticOperatiors(double m, double c);
	protected abstract void initializeFitnessEvaluation(int i) throws not_Legal_Exception, Exception;
	protected abstract void initializePopulation();
	protected abstract void initializeAndDevelopGeneration() throws not_Legal_Exception;
	protected abstract void createNewPopulation();
}
